package org.example.service;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import cn.hutool.core.lang.Pair;
import lombok.extern.slf4j.Slf4j;
import org.example.service.consts.CommonConsts;

/**
 * 将弹幕数据写入csv文件
 */
@Slf4j
public class DanmuCsvWriter {

    /**
     * 弹幕日志存储根路径
     */
    private final String baseDir;

    /**
     * 当前写入的文件映射(<应用名称,房间名称>->文件信息)
     */
    private final ConcurrentHashMap<Pair<String, String>, FileInfo> currentFiles = new ConcurrentHashMap<>();

    /**
     * 文件写入锁映射<应用名称,房间名称>->锁)
     */
    private final ConcurrentHashMap<Pair<String, String>, ReentrantLock> fileWriteLocks = new ConcurrentHashMap<>();

    /**
     * 时间格式化
     */
    private final SimpleDateFormat timeFormatter = new SimpleDateFormat("yyyyMMdd");

    /**
     * csv文件表头
     */
    private final String CSV_HEADER = String.join(",",
            CommonConsts.DanmuEntityFieldKey.APP_NAME_KEY,
            CommonConsts.DanmuEntityFieldKey.DANMU_MSG_KEY,
            CommonConsts.DanmuEntityFieldKey.DANMU_TIMESTAMP_KEY,
            CommonConsts.DanmuEntityFieldKey.DANMU_CREATE_DATE,
            CommonConsts.DanmuEntityFieldKey.ROOM_ID_KEY,
            CommonConsts.DanmuEntityFieldKey.USER_NICKNAME_KEY);

    private static class FileInfo {
        private String timeKey;
        private File file;
        BufferedWriter writer;

        FileInfo(String timeKey, File file) {
            this.timeKey = timeKey;
            this.file = file;
        }
    }

    public DanmuCsvWriter(String baseDir) {
        this.baseDir = baseDir;
        new File(baseDir).mkdirs();
    }

    /**
     * 将弹幕数据写入csv文件
     * 
     * @param appName        应用名称
     * @param roomId         房间ID
     * @param userName       用户名称
     * @param danmuMsg       弹幕内容
     * @param danmuTimestamp 弹幕时间戳
     * 
     */
    public void writeDanmu(Map<String, Object> danmuData) {
        String appName = (String) danmuData.get(CommonConsts.DanmuEntityFieldKey.APP_NAME_KEY);
        String roomId = (String) danmuData.get(CommonConsts.DanmuEntityFieldKey.ROOM_ID_KEY);
        String timeKey = timeFormatter.format(new Date());

        // 获取文件锁
        Pair<String, String> key = new Pair<>(appName, roomId);
        ReentrantLock lock = fileWriteLocks.computeIfAbsent(key, k -> new ReentrantLock());
        lock.lock();
        try {
            FileInfo fileInfo = currentFiles.get(key);
            // 先检查当前是否存在文件或者需要创建新文件
            if (fileInfo == null || !fileInfo.timeKey.equals(timeKey)) {
                if (fileInfo != null) {
                    // 文件存在，先将这个文件的写入流关闭
                    fileInfo.writer.close();
                }

                // 创建新文件
                File newFile = createNewFile(appName, roomId, timeKey);
                BufferedWriter fileWriter = createFileWriter(newFile);

                // 是新文件，写入csv表头
                if (fileInfo == null || !fileInfo.file.exists() || fileInfo.file.length() == 0) {
                    fileWriter.write(CSV_HEADER);
                    fileWriter.newLine();
                }

                fileInfo = new FileInfo(timeKey, newFile);
                fileInfo.writer = fileWriter;
                currentFiles.put(key, fileInfo);
            }
            // 写入弹幕数据
            writeDanmuDataToFile(fileInfo.writer, danmuData);
        }catch(Exception e){
            log.error("writeDanmu error,",e);
        } finally {
            lock.unlock();
        }
    }

    private File createNewFile(String appName, String roomId, String timeKey) {
        String filePath = baseDir + File.separator + appName + File.separator + roomId;
        File dir = new File(filePath);
        if (!dir.exists()) {
            dir.mkdirs();
        }
        String fileName = appName + "_" + roomId + "_" + timeKey + ".csv";
        log.info("新文件路径:{},新文件名:{}",filePath,fileName);
        return new File(dir, fileName);
    }

    private BufferedWriter createFileWriter(File file) {
        try {
            return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), StandardCharsets.UTF_8));

        } catch (IOException e) {
            log.error("Failed to create File writer for file: {}", file.getAbsolutePath(), e);
            throw new RuntimeException("Failed to create File writer for file: " + file.getAbsolutePath());
        }
    }

    private void writeDanmuDataToFile(BufferedWriter writer, Map<String, Object> danmuData) {
        try {
            // 构建csv行
            String line = String.format("%s,%s,%s,%s,%s,%s",
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.APP_NAME_KEY).toString()),
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.DANMU_MSG_KEY).toString()),
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.DANMU_TIMESTAMP_KEY).toString()),
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.DANMU_CREATE_DATE).toString()),
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.ROOM_ID_KEY).toString()),
                    escapeCsvField(danmuData.get(CommonConsts.DanmuEntityFieldKey.USER_NICKNAME_KEY).toString()));
            writer.write(line);
            writer.newLine();

            writer.flush();
        } catch (IOException ex) {
            log.error("write line to file error,", ex);
        }
    }

    /**
     * 处理csv字段中的特殊字符
     */
    private String escapeCsvField(String field) {
        if (field == null) {
            return "";
        }
        if (field.contains(",") || field.contains("\"") || field.contains("\n")) {
            field = field.replace("\"", "\"\"");
            return "\"" + field + "\"";
        }
        return field;

    }

    /**
     * 关闭所有文件写入流
     */

    public void close() {
        for (FileInfo fileInfo : currentFiles.values()) {
            try {
                if (fileInfo.writer != null) {
                    fileInfo.writer.close();
                }
            } catch (IOException e) {
                log.error("close file writer error,", e);
            }
        }
        currentFiles.clear();
    }

}
